# Development Standards
This document outlines the coding standards and patterns used in the ATOM SaaS platform. Following these standards ensures consistency, maintainability, and scalability.
## Table of Contents
---
## Tenant Context Extraction
**CRITICAL**: All multi-tenant API routes MUST extract tenant context to ensure data isolation.
### Frontend Pattern (Next.js)
import { getTenantFromRequest } from '@/lib/tenant/tenant-extractor'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
// ALWAYS extract tenant first
const tenant = await getTenantFromRequest(request)
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 })
}
// Use tenant.id for all database operations
const result = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenant.id]
)
return NextResponse.json(result)
}
### Backend Pattern (FastAPI)
from core.tenant_utils import get_current_tenant
from fastapi import Depends
@router.post("/agents")
async def create_agent(
agent_data: AgentCreate,
tenant_id: str = Depends(get_current_tenant)
):
# FastAPI dependency injection handles tenant extraction
return await create_agent_for_tenant(tenant_id, agent_data)
### When NOT to Extract Tenant
These routes do NOT need tenant extraction:
- Authentication endpoints (/api/auth/*)
- Public health endpoints (/api/health)
- Tenant resolution endpoints (/api/tenant/current)
- NextAuth callbacks (/api/auth/[...nextauth])
---
## Error Handling
### Frontend Error Handling
Use the standardized error classes from @/lib/errors/api-error.ts:
import { ValidationError, NotFoundError, handleApiError } from '@/lib/errors/api-error'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
// Validate input
if (!body.name) {
throw new ValidationError('Name is required')
}
// ... your logic
} catch (error) {
return handleApiError(error) // Automatic NextResponse with correct status
}
}
### Backend Error Handling
Use the standardized error classes from core.error_handler:
from core.error_handler import (
ValidationError,
NotFoundError,
AuthenticationError,
RateLimitError
)
@router.post("/agents/{agent_id}")
async def update_agent(agent_id: str, agent_data: dict):
agent = await get_agent(agent_id)
if not agent:
raise NotFoundError("Agent", agent_id)
if agent_data["name"] == "forbidden":
raise ValidationError("Invalid agent name")
return agent
### Standard Error Response Format
Both frontend and backend use this structure:
{
"success": false,
"error": {
"message": "User-friendly error message",
"code": "VALIDATION_ERROR",
"details": {}
}
}
---
## Database Access
### ❌ NEVER access database directly in route handlers
**Frontend:**
// ❌ BAD
import { getDatabase } from '@/lib/database'
const db = getDatabase()
const result = await db.query('SELECT * FROM agents')
// ✅ GOOD
import { AgentService } from '@/lib/services/agent-service'
const agents = await AgentService.getByTenant(tenant.id)
**Backend:**
# ❌ BAD
@router.get("/agents")
async def get_agents():
db = SessionLocal()
return db.query(Agent).all()
# ✅ GOOD
@router.get("/agents")
async def get_agents(
tenant_id: str = Depends(get_current_tenant),
agent_service: AgentService = Depends()
):
return await agent_service.list_agents(tenant_id)
### Use Service Layer for Business Logic
Encapsulate database logic in service classes:
// services/agent-service.ts
export class AgentService {
static async getByTenant(tenantId: string) {
const db = getDatabase()
const result = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenantId]
)
return result.rows
}
}
---
## API Response Formats
### Success Response
{
"success": true,
"data": { ... }
}
### Error Response
{
"success": false,
"error": {
"message": "User-friendly error",
"code": "ERROR_CODE",
"details": {}
}
}
### Pagination
{
"success": true,
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}
---
## Brain System Usage
The ATOM platform has 6 core brain systems. Use them appropriately:
### When to Use Each Brain System
| System | Purpose | Location |
|--------|---------|----------|
| **Cognitive Architecture** | Human-like reasoning, decision making | src/lib/ai/cognitive-architecture.ts |
| **Learning Engine** | Learn from experience, adapt behavior | src/lib/ai/learning-adaptation-engine.ts |
| **World Model** | Long-term memory, recall experiences | src/lib/ai/world-model.ts |
| **Reasoning Engine** | Proactive intelligence, interventions | src/lib/ai/reasoning-engine.ts |
| **Cross-System Reasoning** | Correlate data across integrations | src/lib/ai/cross-system-reasoning.ts |
| **Agent Governance** | Permission checking, maturity levels | src/lib/ai/agent-governance.ts |
### Brain System Usage Pattern
import { CognitiveArchitecture } from '@/lib/ai/cognitive-architecture'
import { WorldModelService } from '@/lib/ai/world-model'
import { AgentGovernanceService } from '@/lib/ai/agent-governance'
// 1. Recall past experiences
const worldModel = new WorldModelService(db)
const memories = await worldModel.recallExperiences(
tenantId,
agentRole,
taskDescription,
limit: 5
)
// 2. Check permissions
const governance = new AgentGovernanceService(db)
const decision = await governance.canPerformAction(tenantId, agentId, actionType)
if (!decision.allowed) {
throw new Error(`Action not allowed: ${decision.reason}`)
}
// 3. Execute with cognitive support
const cognitive = new CognitiveArchitecture(db)
const reasoning = await cognitive.reason(tenantId, agentId, task, context)
---
## Naming Conventions
### Files
- **Components**: PascalCase with .tsx extension → AgentCard.tsx
- **Utilities**: camelCase with .ts extension → tenantService.ts
- **Types**: PascalCase with .ts extension → AgentConfig.ts
- **Constants**: UPPER_SNAKE_CASE → MAX_AGENTS_FREE
### Directories
- **Components**: kebab-case → agent-dashboard/, training-config-panel/
- **Utilities**: kebab-case → tenant/, ai/, integrations/
### Variables
- **Variables**: camelCase → agentId, tenantService
- **Constants**: UPPER_SNAKE_CASE → MAX_RETRIES, DEFAULT_TIMEOUT
- **Private variables**: camelCase with underscore prefix → _internalState
### Functions
- **Regular functions**: camelCase → getTenant(), validateInput()
- **Async functions**: camelCase → fetchTenantData(), processPayment()
- **Event handlers**: camelCase with handle prefix → handleSubmit(), handleError()
### Classes
- **Classes**: PascalCase → AgentService, TenantExtractor
- **Interfaces**: PascalCase with I prefix → IConfig, IRepository
- **Types**: PascalCase → AgentConfig, TenantContext
### Database
- **Tables**: snake_case → agent_registry, tenant_settings
- **Columns**: snake_case → tenant_id, created_at
- **Indexes**: snake_case → idx_tenant_id, uq_user_email
---
## Testing Standards
### Unit Tests
// services/__tests__/agent-service.test.ts
describe('AgentService', () => {
it('should return agents for tenant', async () => {
const agents = await AgentService.getByTenant('tenant-1')
expect(agents).toHaveLength(3)
})
it('should throw not found for invalid agent', async () => {
await expect(
AgentService.getById('tenant-1', 'invalid-id')
).rejects.toThrow(NotFoundError)
})
})
### Integration Tests
describe('Agent API', () => {
it('should create agent via POST /api/agents', async () => {
const response = await fetch('/api/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test Agent' })
})
expect(response.status).toBe(201)
})
})
### E2E Tests
Located in tests/ directory. Run with:
npm run test:e2e # 212 tests covering critical user flows
---
## Code Organization
### Frontend (Next.js)
src/
├── app/
│ └── api/ # API routes (Next.js 14 App Router)
├── components/ # React components
│ ├── agents/ # Domain-specific components
│ ├── ui/ # Reusable UI components
│ └── canvas/ # Canvas editors
├── lib/
│ ├── ai/ # Brain systems (CRITICAL)
│ ├── integrations/ # OAuth clients
│ ├── tenant/ # Multi-tenant logic
│ └── services/ # Business logic layer
└── styles/ # Global styles
### Backend (FastAPI)
backend-saas/
├── core/ # Business logic
│ ├── models.py # SQLAlchemy models
│ ├── services/ # Service classes
│ └── agent_*/ # Agent-related logic
├── api/ # FastAPI routes
└── alembic/ # Database migrations
---
## Common Patterns
### 1. Dependency Injection Pattern
Use dependency injection for database connections and services:
**Frontend:**
import { getDatabase } from '@/lib/database'
export class AgentService {
constructor(private db = getDatabase()) {}
}
**Backend:**
from fastapi import Depends
from core.database import get_db
@router.get("/agents")
async def get_agents(
db: Session = Depends(get_db)
):
return db.query(Agent).all()
### 2. Tenant Isolation Pattern
**CRITICAL**: Every database query MUST filter by tenant_id:
// ❌ BAD - returns all tenants' data
const agents = await db.query('SELECT * FROM agents')
// ✅ GOOD - filters by tenant
const agents = await db.query(
'SELECT * FROM agents WHERE tenant_id = $1',
[tenant.id]
)
### 3. Error Handling Pattern
Wrap business logic in try-catch and convert to standardized errors:
try {
const agent = await createAgent(data)
return NextResponse.json(agent, { status: 201 })
} catch (error) {
if (error instanceof ValidationError) {
return handleApiError(error)
}
return handleApiError(new InternalServerError('Failed to create agent'))
}
### 4. Async Route Pattern
All route handlers should be async:
export async function GET(request: NextRequest) {
// Always await async operations
const data = await fetchData()
return NextResponse.json(data)
}
---
## Performance Considerations
### 1. Use Server Components for Data Fetching
// ✅ GOOD - Server Component
export default async function AgentsPage() {
const agents = await AgentService.getByTenant(tenantId)
return <AgentList agents={agents} />
}
// ❌ BAD - Client Component fetching
'use client'
export function AgentsPage() {
const [agents, setAgents] = useState([])
useEffect(() => {
fetchAgents().then(setAgents)
}, [])
}
### 2. Implement Rate Limiting
import { AbuseProtectionService } from '@/lib/safety/abuse-protection'
const canProceed = await abuseProtection.checkRateLimit(tenantId)
if (!canProceed) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429 }
)
}
### 3. Use Database Indexes
CREATE INDEX idx_agents_tenant_id ON agents(tenant_id);
CREATE INDEX idx_agents_maturity ON agents(maturity_level) WHERE tenant_id = $1;
---
## Security Best Practices
### 1. Always Validate Input
import { z } from 'zod'
const AgentSchema = z.object({
name: z.string().min(1).max(100),
maturity_level: z.enum(['student', 'intern', 'supervised', 'autonomous'])
})
const validated = AgentSchema.parse(body)
### 2. Never Expose Internal Errors
catch (error) {
// ❌ BAD - exposes stack trace
return NextResponse.json({ error: error.stack })
// ✅ GOOD - logs but doesn't expose
logger.error('Error details:', error)
return NextResponse.json(
{ error: 'An error occurred' },
{ status: 500 }
)
}
### 3. Use Governance Checks
import { AgentGovernanceService } from '@/lib/ai/agent-governance'
const governance = new AgentGovernanceService(db)
const decision = await governance.canPerformAction(tenantId, agentId, 'DELETE')
if (!decision.allowed) {
return NextResponse.json(
{ error: `Action not allowed: ${decision.reason}` },
{ status: 403 }
)
}
---
## Documentation Standards
### Code Comments
- **Public APIs**: Always document with JSDoc/Docstrings
- **Complex logic**: Add explanatory comments
- **TODOs**: Add context and expected behavior
- **FIXMEs**: Add urgency and issue reference
### Component Documentation
Add JSDoc for all exported components:
/**
* Agent Card Component
*
* Displays agent information with actions for run, schedule, plan, and view history.
* Shows performance score, maturity level, and last run status.
*
* @param agent - Agent data to display
* @param onRun - Callback when user clicks run button
* @param onSchedule - Callback when user clicks schedule button
*/
export function AgentCard({ agent, onRun, onSchedule }: AgentCardProps) {
// ...
}
### API Endpoint Documentation
Add JSDoc to all API route functions:
/**
* GET /api/agents
*
* Get all agents for the current tenant.
* Returns paginated list of agents sorted by name.
*
* @query page - Page number (default: 1)
* @query limit - Items per page (default: 20)
* @returns {Promise<NextResponse>} JSON response with agents array
*/
export async function GET(request: NextRequest) {
// ...
}
---
## Review Checklist
Before submitting code, verify:
- [ ] All API routes extract tenant context (if needed)
- [ ] Error responses use standardized format
- [ ] Database queries filter by tenant_id
- [ ] Input validation with Zod schemas
- [ ] Sensitive data is not logged
- [ ] Governance checks for agent actions
- [ ] TypeScript compiles without errors
- [ ] Tests added for new functionality
- [ ] JSDoc comments added for public APIs
- [ ] Code follows naming conventions
---
## Getting Help
### For Code Reviews
1. Ensure all patterns in this document are followed
2. Check for tenant isolation in all database operations
3. Verify error handling is consistent
4. Confirm brain systems are used appropriately
### For Architecture Decisions
1. Document the decision in this file
2. Update relevant architecture docs
3. Team discussion before major changes
---
## Related Documents
- CLAUDE.md - Platform overview
- BRAIN_SYSTEMS.md - Brain system usage
- MULTI_TENANCY.md - Tenant architecture
- API_STANDARDS.md - API design patterns
Last updated: 2025-02-03